package com.tjvib.sensor;

import static com.tjvib.Constants.SUCCESS;

import android.content.Context;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

import com.tjvib.bean.DataSet;
import com.tjvib.bean.DataSetInfo;
import com.tjvib.common.Callback;
import com.tjvib.util.FileUtil;
import com.tjvib.util.LogUtil;
import com.tjvib.util.Manager.AppManager;
import com.tjvib.util.Manager.UserManager;
import com.tjvib.util.ToastUtil;
import com.tjvib.util.retrofit.BaseResponse;
import com.tjvib.util.retrofit.RetrofitHelper;
import com.tjvib.view.activity.LocalSensorActivity;

import org.json.JSONException;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import okhttp3.MediaType;
import okhttp3.RequestBody;

/**
 * 手机本地传感器
 */
public class LocalSensor extends Sensor<LocalSensorData> implements SensorEventListener {

    private static final float GRAVITY_CONST = 9.8f;

    private long firstTimestamp = 0;

    private static Context context;

    private SensorManager sensorManager;

    private Timer timer;

    private static LocalSensorData currentData = new LocalSensorData();

    private static LocalSensor instance = new LocalSensor();

    private static ArrayList<LocalSensorData> records = new ArrayList<>();

    private static boolean isRecording;

    private LocalSensor() {}

    public static LocalSensor getInstance(Context context) {
        LocalSensor.context = context;
        currentData = new LocalSensorData();
        records = new ArrayList<>();
        return instance;
    }

    @Override
    public boolean connect() {
        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        if (sensorManager == null) {
            return false;
        }

        android.hardware.Sensor accelerometer = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ACCELEROMETER);
        android.hardware.Sensor linearAccelerometer = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_LINEAR_ACCELERATION);
        android.hardware.Sensor gyroscope = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_GYROSCOPE);
        android.hardware.Sensor magnetic = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_MAGNETIC_FIELD);
        android.hardware.Sensor rotationVector = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ROTATION_VECTOR);

        if (!sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST)) {
            ToastUtil.show("本设备不支持动态检测");
            LogUtil.e("sensorManager !registerListener accelerometer");
            return false;
        }
        if (!sensorManager.registerListener(this, linearAccelerometer, SensorManager.SENSOR_DELAY_FASTEST)) {
            ToastUtil.show("本设备不支持监测单个轴向上的加速度");
            LogUtil.e("sensorManager !registerListener linearAccelerometer");
            return false;
        }
//        if (!sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST)) {
//            ToastUtil.show("本设备不支持旋转检测");
//            LogUtil.e("sensorManager !registerListener gyroscope");
//            return false;
//        }
        if (!sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_FASTEST)) {
            ToastUtil.show("本设备不支持创建罗盘");
            LogUtil.e("sensorManager !registerListener magnetic");
            return false;
        }
        if (!sensorManager.registerListener(this, rotationVector, SensorManager.SENSOR_DELAY_FASTEST)) {
            ToastUtil.show("本设备不支持动态检测和旋转检测");
            LogUtil.e("sensorManager !registerListener rotationVector");
            return false;
        }

        resetTimestamp();
        timer = new Timer();
        timer.scheduleAtFixedRate(new ReadDataTask(), 0, (long) (1.0 / streamingFrequency * 1000));
        connectionStatus = SENSOR_STATUS_CONNECTED;
        return true;
    }

    @Override
    public void disconnect() {
        timer.cancel();
        timer = null;
        sensorManager.unregisterListener(this);
        connectionStatus = SENSOR_STATUS_DISCONNECTED;
    }

    @Override
    public void setStreamFrequency(int freq) {
        streamingFrequency = freq;
    }

    @Override
    public void resetTimestamp() {
        firstTimestamp = System.currentTimeMillis();
        if(LocalSensorActivity.firstTimestamp == 0)
            LocalSensorActivity.firstTimestamp = firstTimestamp;
    }

    public static void discardData() {
        records.clear();
    }

    public static void storeData(String name, Callback callback) {
        DataSetInfo info = new DataSetInfo(-1, name, "local", false,
                new Timestamp(System.currentTimeMillis()).toString());
        DataSet<LocalSensorData> dataSet = new DataSet<>(info, records);
        new Thread(() -> {
            String dataSetStr = null;
            try {
                dataSetStr = dataSet.toJsonString();
            } catch (JSONException e) {
                e.printStackTrace();
            }
            RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), dataSetStr);
            RetrofitHelper.getInstance().create()
                    .collectLocal("" + UserManager.getInstance().getUserId(), requestBody)
                    .subscribe(new Observer<BaseResponse>() {
                        @Override
                        public void onSubscribe(Disposable d) {
                            LogUtil.e("onSubscribe "+d);
                        }

                        @Override
                        public void onNext(BaseResponse value) {
                            if (value.getCode() == SUCCESS) {
                                callback.onSuccess(value.getMessage(), null);
                                FileUtil.deleteDataSetTempFile(context);
                            } else {
                                callback.onFail(value.getMessage(), null);
                            }
                            LogUtil.i("collectLocal "+value.getCode() + " " + value.getMessage() + " " + value.getData());
                        }

                        @Override
                        public void onError(Throwable e) {
                            if (e != null) {
                                ToastUtil.show("e=" + e.getMessage());
                                LogUtil.e("e=" + e.getMessage());
                            } else {
                                LogUtil.e("null");
                            }
                        }

                        @Override
                        public void onComplete() {
                            LogUtil.e("onComplete");
                        }
                    });
        }).start();
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        long temp;
        switch (event.sensor.getType()) {
            case android.hardware.Sensor.TYPE_ACCELEROMETER:
                currentData.localtime = System.currentTimeMillis();
                temp = currentData.localtime - firstTimestamp;
                currentData.timestamp = temp / 1000f;
                currentData.acc[0] = event.values[0] / GRAVITY_CONST;
                currentData.acc[1] = event.values[1] / GRAVITY_CONST;
                currentData.acc[2] = event.values[2] / GRAVITY_CONST;
                break;
            case android.hardware.Sensor.TYPE_LINEAR_ACCELERATION:
                currentData.localtime = System.currentTimeMillis();
                temp = currentData.localtime - firstTimestamp;
                currentData.timestamp = temp / 1000f;
                currentData.linAcc[0] = event.values[0] / GRAVITY_CONST;
                currentData.linAcc[1] = event.values[1] / GRAVITY_CONST;
                currentData.linAcc[2] = event.values[2] / GRAVITY_CONST;
                break;
            case android.hardware.Sensor.TYPE_GYROSCOPE:
                currentData.localtime = System.currentTimeMillis();
                temp = currentData.localtime - firstTimestamp;
                currentData.timestamp = temp / 1000f;
                currentData.gyr[0] = event.values[0];
                currentData.gyr[1] = event.values[1];
                currentData.gyr[2] = event.values[2];
                break;
            case android.hardware.Sensor.TYPE_MAGNETIC_FIELD:
                currentData.localtime = System.currentTimeMillis();
                temp = currentData.localtime - firstTimestamp;
                currentData.timestamp = temp / 1000f;
                currentData.mag[0] = event.values[0];
                currentData.mag[1] = event.values[1];
                currentData.mag[2] = event.values[2];
                break;
            case android.hardware.Sensor.TYPE_ROTATION_VECTOR:
                currentData.localtime = System.currentTimeMillis();
                temp = currentData.localtime - firstTimestamp;
                currentData.timestamp = temp / 1000f;
                currentData.rotationVector[0] = event.values[0];
                currentData.rotationVector[1] = event.values[1];
                currentData.rotationVector[2] = event.values[2];
                currentData.rotationVector[3] = event.values[3];
                currentData.rotationVector[4] = event.values[4];
                SensorManager.getQuaternionFromVector(currentData.quat, currentData.rotationVector);
                break;
        }
    }

    @Override
    public void onAccuracyChanged(android.hardware.Sensor sensor, int accuracy) {

    }

    @Override
    public LocalSensorData getData() {
        LocalSensorData data = null;
        synchronized (dataQueue) {
            while (dataQueue.size() > 0) {
                data = dataQueue.getLast();
                dataQueue.removeLast();
            }
        }
        return data;
    }

    public void startRecording(Context context) {
        isRecording = true;
        tmpFileWriter = FileUtil.getDataSetTempFileWriter(context);
        try {
            if (tmpFileWriter != null) {
                tmpFileWriter.append("local\n");
            }
            LogUtil.w("tmpFileWriter="+tmpFileWriter);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stopRecording() {
        isRecording = false;
        try {
            if (tmpFileWriter != null) {
                tmpFileWriter.close();
                tmpFileWriter = null;
            }
            LogUtil.w("tmpFileWriter="+tmpFileWriter);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean isRecording() {
        return isRecording;
    }

    class ReadDataTask extends TimerTask {
        @Override
        public void run() {
            LogUtil.i("ReadDataTask tmpFileWriter=" + tmpFileWriter);
            if (isRecording && tmpFileWriter != null) {
                records.add(new LocalSensorData(currentData));
                try {
                    tmpFileWriter.append(String.valueOf(currentData.localtime)).append(",")
                            .append(String.valueOf(currentData.timestamp)).append(",")
                            .append(String.valueOf(currentData.acc[0])).append(",")
                            .append(String.valueOf(currentData.acc[1])).append(",")
                            .append(String.valueOf(currentData.acc[2])).append(",")
                            .append(String.valueOf(currentData.gyr[0])).append(",")
                            .append(String.valueOf(currentData.gyr[1])).append(",")
                            .append(String.valueOf(currentData.gyr[2])).append(",")
                            .append(String.valueOf(currentData.mag[0])).append(",")
                            .append(String.valueOf(currentData.mag[1])).append(",")
                            .append(String.valueOf(currentData.mag[2])).append(",")
                            .append(String.valueOf(currentData.quat[0])).append(",")
                            .append(String.valueOf(currentData.quat[1])).append(",")
                            .append(String.valueOf(currentData.quat[2])).append(",")
                            .append(String.valueOf(currentData.quat[3])).append(",")
                            .append(String.valueOf(currentData.linAcc[0])).append(",")
                            .append(String.valueOf(currentData.linAcc[1])).append(",")
                            .append(String.valueOf(currentData.linAcc[2])).append("\n");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            dataQueue.addFirst(new LocalSensorData(currentData));
        }
    }
}
